とりあえず MediaLive を触りたい時に使える CloudFormation テンプレートを作ってみた
こんにちは、大前です。今日も今日とて MediaServices を触っていきます。
背景
私は MediaServices のブログを書くことが多いため、事あるごとに MediaServices のリソースを作成しているのですが、多くのケースでは検証するために複数のサービス(MediaLive+MediaStore+CloudFrontとか)を立てる必要があり、何かと面倒でした。
また、MediaLive の Input と Channel に関してはアイドル状態でも微課金が発生してしまうため、毎回検証が終わる度に削除する必要があります。
アイドルリソース AWS Elemental MediaLive charges for each push input and channel when these resources are not in use. Only push inputs, RTP PUSH and RTMP PUSH, incur a cost when idle. Pulled inputs, HLS PULL and RTMP PULL, do not incur an idle resource cost. The pricing for idle resources is:
0.01USD per hour on a pro rata basis for each push input not associated with a running channel 0.01USD per hour on a pro rata basis for each channel or statmux pool not in a running state
正直、何回も作成しているので特にドキュメント等を見なくても大枠のリソースは作成出来る様になってしまったのですが、CloudFormation で Media 系のサービスを作成してみたいなという気持ちもあり、この度 CloudFormation テンプレートを作成してみました。
タイトルにある通り、「とりあえず触りたい」場合向けなので細かい設定値は適当な部分が多いです。
そのため、本番ワークフロー等に活用する場合には設定値の確認・検討はしっかりと実施ください。
やってみた
CloudFormationで作成する範囲
CloudFormationでは以下図の水色背景部分を作成します。
作成される主なリソースとしては、MediLive、IAM Role、MediaStore の3つです。
MediaLive の入力は RTMP を受け付ける形で作成されるため、OBS 等からのライブ配信がすぐに行える環境が作成できます。
作成後は、MediaLive の出力設定を弄ってみたり、CloudFront を追加してみたりと自由に遊べます。
CloudFormationテンプレート
以下がテンプレートになります。
個人的には、MediaLive に設定する IAM Role の作成や、MediaStore への CORS 設定等をテンプレートに落とし込めたのが嬉しいポイントです。
また、削除ポリシーはあえて設定していないので、不要になったら CloudFormation のスタックを削除すれば全リソースが削除されます。(スタック作成後に手動で設定を変更していたりすると、うまく削除できない場合があります)
使用する際には適宜修正してお使いください。
AWSTemplateFormatVersion: '2010-09-09' Parameters: #ProjectName ProjectName: Description: "Name of MediaLiveInput" Default: "Sample" Type: String #StreamKey StreamKey: Description: "StreamKeyA of Input" Type: String Default: "streamkey" #ChannelClass ChannelClass: Description: "ChannelClass" Type: String Default: "SINGLE_PIPELINE" AllowedValues: - STANDARD - SINGLE_PIPELINE #SecurityGroupWhitelistCIDR SecurityGroupWhitelistCIDR: Description: "Whitelist CIDR of MediaLiveInputSecurityGroup." Type: String Default: "0.0.0.0/0" #MediaLiveDestinationName MediaLiveDestinationName: Description: "MediaLive DestinationName." Type: String Default: "MediaLiveDest1" Resources: # ---------------- # MediaLiveInput # ---------------- MediaLiveInput: Type: AWS::MediaLive::Input Properties: Destinations: - StreamName: !Sub "${ProjectName}/${StreamKey}-A" - StreamName: !Sub "${ProjectName}/${StreamKey}-B" InputSecurityGroups: - !Ref MediaLiveInputSecurityGroup Name: !Sub "${ProjectName}-input" Tags: { 'Name' : !Sub "${ProjectName}-input"} Type: "RTMP_PUSH" # ----------------------------- # MediaLiveInputSecurityGroup # ----------------------------- MediaLiveInputSecurityGroup: Type: AWS::MediaLive::InputSecurityGroup Properties: Tags: { 'Name' : !Sub "${ProjectName}-inputsg"} WhitelistRules: - Cidr: !Ref SecurityGroupWhitelistCIDR # ------------------ # MediaLiveChannel # ------------------ MediaLiveChannel: Type: AWS::MediaLive::Channel Properties: ChannelClass: !Ref ChannelClass Destinations: - Id: !Ref MediaLiveDestinationName Settings: - PasswordParam: "" StreamName: "" Url: !Sub - "mediastoressl://${MediaStoreEndpoint}/${ProjectName}/destA" - MediaStoreEndpoint: !Select [ 1, !Split [ "//", !GetAtt MediaStore.Endpoint ]] Username: "" EncoderSettings: AudioDescriptions: - AudioSelectorName: "default" CodecSettings: AacSettings: Bitrate: 96000 RawFormat: "NONE" Spec: "MPEG4" AudioTypeControl: "FOLLOW_INPUT" LanguageCodeControl: "FOLLOW_INPUT" Name: "audio_3_aac96" OutputGroups: - OutputGroupSettings: HlsGroupSettings: CaptionLanguageSetting: "OMIT" HlsCdnSettings": HlsBasicPutSettings: NumRetries: 5 ConnectionRetryInterval: 30 RestartDelay: 5 FilecacheDuration: 300 InputLossAction: "EMIT_OUTPUT" ManifestCompression: "NONE" Destination: DestinationRefId: !Ref MediaLiveDestinationName IvInManifest: "INCLUDE" IvSource: "FOLLOWS_SEGMENT_NUMBER" ClientCache: "ENABLED" TsFileMode: "SEGMENTED_FILES" ManifestDurationFormat: "FLOATING_POINT" SegmentationMode: "USE_SEGMENT_DURATION" RedundantManifest: "DISABLED" OutputSelection: "MANIFESTS_AND_SEGMENTS" StreamInfResolution: "INCLUDE" IFrameOnlyPlaylists: "DISABLED" IndexNSegments: 10 ProgramDateTime: "INCLUDE" ProgramDateTimePeriod: 600 KeepSegments: 21 SegmentLength: 6 TimedMetadataId3Frame: "PRIV" TimedMetadataId3Period: 10 HlsId3SegmentTagging: "DISABLED" CodecSpecification: "RFC_4281" DirectoryStructure: "SINGLE_DIRECTORY" SegmentsPerSubdirectory: 10000 Mode: "LIVE" Name: "TN2224" Outputs: - OutputSettings: HlsOutputSettings: NameModifier: "_1280x720_3300k" HlsSettings: StandardHlsSettings: M3u8Settings: AudioFramesPerPes: 4 AudioPids: "492-498" EcmPid: "8182" NielsenId3Behavior: "NO_PASSTHROUGH" PcrControl: "PCR_EVERY_PES_PACKET" PmtPid: "480" ProgramNum: 1 Scte35Pid: "500" Scte35Behavior: "NO_PASSTHROUGH" TimedMetadataPid: "502" TimedMetadataBehavior: "NO_PASSTHROUGH" VideoPid: "481" AudioRenditionSets: "program_audio" H265PackagingType: "HVC1" VideoDescriptionName: "video_1280_720_1" AudioDescriptionNames: - "audio_3_aac96" TimecodeConfig: Source: "SYSTEMCLOCK" VideoDescriptions: - CodecSettings: H264Settings: AfdSignaling: "NONE" ColorMetadata: "INSERT" AdaptiveQuantization: "HIGH" Bitrate: 3300000 EntropyEncoding: "CABAC" FlickerAq: "ENABLED" FramerateControl: "SPECIFIED" FramerateNumerator: 30000 FramerateDenominator: 1001 GopBReference: "ENABLED" GopClosedCadence: 1 GopNumBFrames: 3 GopSize: 60 GopSizeUnits: "FRAMES" SubgopLength: "FIXED" ScanType: "PROGRESSIVE" Level: "H264_LEVEL_4_1" LookAheadRateControl: "HIGH" NumRefFrames: 1 ParControl: "INITIALIZE_FROM_SOURCE" Profile: "HIGH" RateControlMode: "CBR" Syntax: "DEFAULT" SceneChangeDetect: "ENABLED" SpatialAq: "ENABLED" TemporalAq: "ENABLED" TimecodeInsertion: "DISABLED" Height: 720 Name: "video_1280_720_1" RespondToAfd: "NONE" Sharpness: 50 ScalingBehavior: "DEFAULT" Width: 1280 InputAttachments: - InputAttachmentName: !Sub "${ProjectName}-channelinput" InputId: !Ref MediaLiveInput InputSettings: DeblockFilter: "DISABLED" DenoiseFilter: "DISABLED" FilterStrength: 1 InputFilter: "AUTO" SourceEndBehavior: "CONTINUE" InputSpecification: Codec: "AVC" MaximumBitrate: "MAX_20_MBPS" Resolution: "HD" Name: !Sub "${ProjectName}-channel" RoleArn: !GetAtt MediaLiveAccessRole.Arn Tags: { 'Name' : !Sub "${ProjectName}-channel"} # ------------ # MediaStore # ------------ MediaStore: Type: AWS::MediaStore::Container Properties: ContainerName: !Sub "${ProjectName}-container" CorsPolicy: - AllowedHeaders: - "*" AllowedMethods: - "GET" - "HEAD" AllowedOrigins: - "*" ExposeHeaders: - String MaxAgeSeconds: 3000 LifecyclePolicy: '{ "rules": [ { "definition": { "path": [ { "prefix": "" } ], "days_since_create": [ {"numeric": [">" , 5]} ] }, "action": "EXPIRE" } ] }' Policy: !Sub - '{ "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadOverHttps", "Effect": "Allow", "Action": ["mediastore:GetObject", "mediastore:DescribeObject"], "Principal": "*", "Resource": "arn:aws:mediastore:${AWS::Region}:${AWS::AccountId}:container/${CONTAINER}/*", "Condition": { "Bool": { "aws:SecureTransport": "true" } } } ] }' - CONTAINER: !Sub "${ProjectName}-container" # --------------------- # MediaLiveAccessRole # --------------------- MediaLiveAccessRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "medialive.amazonaws.com" Action: - "sts:AssumeRole" ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess Path: / Policies: - PolicyName: !Sub "${ProjectName}-MediaLiveCustomPolicy" PolicyDocument: Statement: - Effect: Allow Action: - s3:ListBucket - s3:PutObject - s3:GetObject - s3:DeleteObject Resource: '*' - Effect: Allow Action: - mediastore:ListContainers - mediastore:PutObject - mediastore:GetObject - mediastore:DeleteObject - mediastore:DescribeObject Resource: '*' - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - logs:DescribeLogStreams - logs:DescribeLogGroups Resource: 'arn:aws:logs:*:*:*' - Effect: Allow Action: - mediaconnect:ManagedDescribeFlow - mediaconnect:ManagedAddOutput - mediaconnect:ManagedRemoveOutput Resource: '*' - Effect: Allow Action: - ec2:describeSubnets - ec2:describeNetworkInterfaces - ec2:createNetworkInterface - ec2:createNetworkInterfacePermission - ec2:deleteNetworkInterface - ec2:deleteNetworkInterfacePermission - ec2:describeSecurityGroups Resource: '*' - Effect: Allow Action: - mediapackage:DescribeChannel Resource: '*' RoleName: !Sub "${ProjectName}-MediaLiveAccessRole"
終わりに
とりあえず MediaLive 触りたい時に使えそうな CloudFormation テンプレートを作成してみました。
aws-samples ではカスタムリソースを使って MediaLive を作成していたりして、私が調べた限りだと純粋な CloudFormation のサンプルが見つかりませんでした。
ですので、「CloudFormation で MediaLive 立ててみたいけどよくわからん。。。」な方にこのテンプレートが役に立つ事を願っております。
以上、AWS 事業本部の大前でした。
余談
Terraform は MediaLive に対応していないのですが、調べると feature request の issue があるそうです。
feature request: medialive resources #4936
需要が高まれば、そのうち実装されるのかもしれませんね。楽しみに待ちたいと思います。